/*
 * Functions to interract with host
 */
#include "precomp.h"
#pragma hdrstop

#include "mrxglobs.h"
#include "host_access.h"
#include "viofs_call.h"
#include "fuse_structs.h"

#define FUSE_DEFAULT_MAX_PAGES 32

// Allow use of ExAllocatePoolWithTag()
#pragma warning(disable:4996)

static
PCHAR
AllocateUtf8Path(
    IN  PUNICODE_STRING         pFileName,
    IN  BOOLEAN                 SkipLeadSlash);

static
VOID
FreeUtf8Path(
    IN PCHAR path);

static
NTSTATUS
FuseLookupRequest(
    IN  PHWFS_DEVICE_EXTENSION  pDevExt,
    IN  UINT32                  ShareId,
    IN  UINT64                  FuseNodeId, // Directory where lookup for the file
    IN  PCHAR                   FileName,   // only file name in UTF-8 without path!
    OUT PFUSE_ENTRY_OUT         pEntry);


NTSTATUS
FuseForgetRequest(
    IN  PHWFS_DEVICE_EXTENSION  pDevExt,
    IN  UINT32                  ShareId,
    IN  UINT64                  FuseNodeId,
    IN  UINT64                  NLookup);

static
NTSTATUS
FuseMapError(int error)
{
    NTSTATUS Status;

    if (error >= 0) {
        return STATUS_SUCCESS;
    }

    switch (error) {
        case -LIN_EPERM:
        case -LIN_EACCES:
            Status = STATUS_ACCESS_DENIED;
            break;
        case -LIN_ENOENT:
            Status = STATUS_OBJECT_NAME_NOT_FOUND;
            break;
        case -LIN_EIO:
            Status = STATUS_IO_DEVICE_ERROR;
            break;
        case -LIN_EBADF:
            Status = STATUS_OBJECT_NAME_INVALID;
            break;
        case -LIN_ENOMEM:
            Status = STATUS_INSUFFICIENT_RESOURCES;
            break;
        case -LIN_ENODEV:
            Status = STATUS_INVALID_HANDLE;
            break;
        case -LIN_EEXIST:
            Status = STATUS_OBJECT_NAME_COLLISION;
            break;
        case -LIN_EINVAL:
            Status = STATUS_INVALID_PARAMETER;
            break;
        case -LIN_ENAMETOOLONG:
            Status = STATUS_NAME_TOO_LONG;
            break;
        case -LIN_ENOSYS:
            Status = STATUS_NOT_IMPLEMENTED;
            break;
        case -LIN_EOPNOTSUPP:
            Status = STATUS_NOT_SUPPORTED;
            break;
        case -LIN_ETXTBSY:
            Status = STATUS_SHARING_VIOLATION;
            break;
        default:
            Status = STATUS_UNSUCCESSFUL;
            break;
    }

    return Status;
}

static
UINT64
FuseUniqueId()
{
    static volatile __int64 opId = 1;
    return (UINT64)InterlockedIncrement64(&opId);
}

static
VOID
FuseHeaderInit(
    PVIOFS_INKERNEL_REQUEST pRequest,
    UINT32 share_id,
    UINT32 opcode,
    UINT64 nodeid,
    ULONG datalen)
{
#define HWFS_FUSE_FAKE_PID 32768

    PVIOFSCALL_EXTENSION pRequestExt = pRequest->CallerContext;
    PFUSE_IN_HEADER hdr = pRequestExt->FuseInHdr;
    pRequest->FuseOpcode = opcode;
    hdr->len = sizeof(*hdr) + datalen;
    hdr->opcode = opcode;
    hdr->unique = FuseUniqueId();
    hdr->nodeid = nodeid;
    hdr->uid = share_id;
    hdr->gid = 0;
    hdr->pid = HWFS_FUSE_FAKE_PID;
}


static
NTSTATUS
FuseInitSession(
    IN  PHWFS_DEVICE_EXTENSION  pDevExt)
{
    NTSTATUS Status;
    PVIOFS_INKERNEL_REQUEST pRequest;
    PVIOFSCALL_EXTENSION pRequestExt;
    FUSE_INIT_IN*  pFuseInitIn;
    FUSE_INIT_OUT* pFuseInitOut;

    HWFS_DBG_PRINT(DBG_INFO, DBG_SESSION, ("FuseInitSession...\n"));

    pDevExt->FuseTimeout.QuadPart = -1 * SUBMIT_VIOFS_TIMEOUT;
    pDevExt->FuseTimeoutPtr = &pDevExt->FuseTimeout;

    pRequest = AllocViofsRequestWithBuffer(
        pDevExt,
        sizeof(*pFuseInitIn),
        sizeof(*pFuseInitOut),
        NULL, 0, NULL, 0);
    if (NULL == pRequest) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    pRequestExt = pRequest->CallerContext;

    FuseHeaderInit(pRequest, 0, FUSE_INIT, FUSE_ROOT_ID, sizeof(pFuseInitIn->init));
    pFuseInitIn = (FUSE_INIT_IN *)pRequestExt->FuseInHdr;
    pFuseInitIn->init.major = FUSE_KERNEL_VERSION;
    pFuseInitIn->init.minor = FUSE_KERNEL_MINOR_VERSION;
    pFuseInitIn->init.max_readahead = 0;
    pFuseInitIn->init.flags = FUSE_DO_READDIRPLUS | FUSE_MAX_PAGES;

    Status = SubmitViofsRequest(pDevExt, pRequest);
    // may be need pay attention to STATUS_DATA_OVERRUN
    if (!NT_SUCCESS(Status)) {
        goto finish;
    }

    Status = FuseMapError(pRequestExt->FuseOutHdr->error);
    if (!NT_SUCCESS(Status)) {
        goto finish;
    }

    pFuseInitOut = (FUSE_INIT_OUT *)pRequestExt->FuseOutHdr;

    if (pRequestExt->BytesReturned < sizeof(*pFuseInitOut)) {
        Status = STATUS_DATA_OVERRUN;
        goto finish;
    }

    pDevExt->FuseMaxWrite = pFuseInitOut->init.max_write;
    pDevExt->FuseMaxPages = pFuseInitOut->init.max_pages ?
        pFuseInitOut->init.max_pages : FUSE_DEFAULT_MAX_PAGES;

    HWFS_DBG_PRINT(DBG_INFO, DBG_ANY, ("Session established. MaxWrite = 0x%x\n",
        pDevExt->FuseMaxWrite));

finish:
    FreeViofsRequest(pRequest);

    if (!NT_SUCCESS(Status)) {
        HWFS_DBG_ENDFUNC(Status);
    }

    return Status;
}


NTSTATUS
HostUpdateVolumeInfo(
    IN     PHWFS_DEVICE_EXTENSION  pDevExt,
    IN OUT PHWFS_SHARE             pShare,
    IN     BOOLEAN                 ForcedUpdate)
{
    NTSTATUS Status;
    PVIOFS_INKERNEL_REQUEST pRequest;
    PVIOFSCALL_EXTENSION pRequestExt;
    UINT64 CurrentTimestamp;

    // cache interval = 100ms
#define VOLINFO_CACHE_INTERVAL_100NS (100*10*1000)

    CurrentTimestamp = KeQueryInterruptTime();
    if (!ForcedUpdate) {
        INT64 Diff = CurrentTimestamp - pShare->VolInfo.UpdateTs;
        if (Diff < 0)
            Diff = -Diff;
        if (Diff < VOLINFO_CACHE_INTERVAL_100NS)
            return STATUS_SUCCESS;
    }

    pRequest = AllocViofsRequestWithBuffer(
        pDevExt,
        sizeof(FUSE_IN_HEADER),
        sizeof(FUSE_STATFS_OUT),
        NULL, 0, NULL, 0);
    if (NULL == pRequest) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    FuseHeaderInit(pRequest, pShare->ShareId, FUSE_STATFS, pShare->RootId, 0);

    Status = SubmitViofsRequest(pDevExt, pRequest);
    if (NT_SUCCESS(Status)) {
        UINT64 BlockSize;
        pRequestExt = pRequest->CallerContext;
        struct fuse_statfs_out* out = (struct fuse_statfs_out*)(pRequestExt->FuseOutHdr + 1);

        pShare->VolInfo.UpdateTs = CurrentTimestamp;

        BlockSize = out->st.bsize;
        if (BlockSize == 0)
            BlockSize = LIN_STATFS_BLOCK_SIZE;

        pShare->VolInfo.BytesTotal = out->st.blocks * BlockSize;
        pShare->VolInfo.BytesFree = out->st.bfree * BlockSize;
        pShare->VolInfo.BytesAvail = out->st.bavail * BlockSize;

        HWFS_DBG_PRINT(DBG_INFO, DBG_ANY,
            ("GetVolumeInfo(): Blocks: 0x%x; Free 0x%x; namelen %d; BlockSize 0x%x\n",
                out->st.blocks, out->st.bfree, out->st.namelen, out->st.bsize));
    }

    FreeViofsRequest(pRequest);
    return Status;
}

NormalizeShareName(
    IN  PUNICODE_STRING     pInputName,
    OUT PUNICODE_STRING     pOutputName)
{
    //
    // strip leading and trailing '\\'
    //
    pOutputName->Buffer = pInputName->Buffer;
    pOutputName->Length = pInputName->Length;
    while (pOutputName->Length > 1) {
        if (pOutputName->Buffer[0] != '\\' && pOutputName->Buffer[0] != '/') {
            break;
        }
        pOutputName->Buffer++;
        pOutputName->Length -= sizeof(WCHAR);
    }
    USHORT strippedLen = 0;
    while (strippedLen < pOutputName->Length) {
        WCHAR curr = pOutputName->Buffer[strippedLen / 2];
        if (curr == '\0' || curr == '\\' || curr == '/') {
            break;
        }
        strippedLen += sizeof(WCHAR);
    }
    pOutputName->Length = strippedLen;
}

VOID
HostInitShareStruct(
    PHWFS_SHARE             pShare)
{
    pShare->ShareGeneration = 0;
    pShare->ShareState = 0;
}

NTSTATUS
HostAccessInit(
    PHWFS_DEVICE_EXTENSION  pDevExt)
{
    NTSTATUS Status;

    if (pDevExt->FuseSessionInitialized) {
        return STATUS_SUCCESS;
    }

    Status = OpenViofsDevice(pDevExt);
    if (!NT_SUCCESS(Status)) {
        HWFS_DBG_PRINT(DBG_ERR, DBG_ANY, ("Failed to connect to virtiofs\n"));
        return STATUS_NETWORK_BUSY;
    }

    Status = FuseInitSession(pDevExt);
    if (!NT_SUCCESS(Status)) {
        HWFS_DBG_PRINT(DBG_ERR, DBG_ANY, ("Failed to initialize session\n"));
        return STATUS_NETWORK_BUSY;
    }

    pDevExt->FuseSessionInitialized = TRUE;

    return STATUS_SUCCESS;
}

static
NTSTATUS
ConnectShareHelper(
    PHWFS_DEVICE_EXTENSION  pDevExt,
    PHWFS_SHARE             pShare)
{
    NTSTATUS       Status;
    FUSE_ENTRY_OUT LookupEntry;
    PCHAR          szShareName;

    szShareName = AllocateUtf8Path(&pShare->ShareName, TRUE);
    if (szShareName == NULL) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    Status = FuseLookupRequest(pDevExt, 0, FUSE_ROOT_ID, szShareName, &LookupEntry);
    FreeUtf8Path(szShareName);

    if (!NT_SUCCESS(Status)) {
        return STATUS_BAD_NETWORK_NAME;
    }

    pShare->RootId = LookupEntry.nodeid;
    pShare->ShareId = LookupEntry.attr.rdev;
    pShare->FuseGeneration = LookupEntry.generation;

    HostShareSetConnected(pShare, TRUE);

    RtlZeroMemory(&pShare->VolInfo, sizeof(pShare->VolInfo));
    HostUpdateVolumeInfo(pDevExt, pShare, TRUE);

    pShare->VolInfo.IsReadOnly = (LookupEntry.attr.flags & HWFS_SHARE_FLAG_READ_ONLY) != 0;
    pShare->VolInfo.IsCaseInsensitive = (LookupEntry.attr.flags & HWFS_SHARE_FLAG_CASE_INSENSITIVE) != 0;

    return STATUS_SUCCESS;
}

BOOLEAN
HostShareIsConnected(
    IN  PHWFS_SHARE             pShare)
{
    return (pShare->ShareState & HWFS_SHARE_STATE_CONNECTED) != 0;
}

VOID
HostShareSetConnected(
    IN  PHWFS_SHARE             pShare,
    IN  BOOLEAN                 isConnected)
{
    if (isConnected) {
        pShare->ShareState |= HWFS_SHARE_STATE_CONNECTED;
    } else {
        pShare->ShareState &= ~HWFS_SHARE_STATE_CONNECTED;
    }
}


NTSTATUS
HostConnectShare(
    PHWFS_DEVICE_EXTENSION  pDevExt,
    PUNICODE_STRING         pFolderName,
    PHWFS_SHARE             pShare)
{
    NTSTATUS Status;
    UNICODE_STRING NormalizedShareName;

    HWFS_DBG_PRINT(DBG_INFO, DBG_SESSION, ("Connect share '%wZ', pShare = %p\n", pFolderName, pShare));

    ExInitializeFastMutex(&pShare->ReconnectMutex);

    pShare->ShareGeneration = 0;

    pShare->ShareState = 0;

    Status = HostAccessInit(pDevExt);
    if (!NT_SUCCESS(Status)) {
        return STATUS_NETWORK_BUSY;
    }

    NormalizeShareName(pFolderName, &NormalizedShareName);
    if (NormalizedShareName.Length == 0) {
        HWFS_DBG_PRINT(DBG_ERR, DBG_ANY, ("Cannot connect share with zero name '%wZ'\n", pFolderName));
        return STATUS_BAD_NETWORK_NAME;
    }

    pShare->ShareName.Buffer = ExAllocatePoolWithTag(PagedPool, NormalizedShareName.Length, 'mNhS');
    if (pShare->ShareName.Buffer == NULL) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    RtlCopyMemory(pShare->ShareName.Buffer, NormalizedShareName.Buffer, NormalizedShareName.Length);
    pShare->ShareName.Length = pShare->ShareName.MaximumLength = NormalizedShareName.Length;

    Status = ConnectShareHelper(pDevExt, pShare);
    if (!NT_SUCCESS(Status)) {
        HWFS_DBG_PRINT(DBG_ERR, DBG_ANY, ("Failed to connect share '%wZ: 0x%x'\n",
            pFolderName, Status));

        ExFreePoolWithTag(pShare->ShareName.Buffer, 'mNhS');
        return STATUS_BAD_NETWORK_NAME;
    }

    HWFS_DBG_PRINT(DBG_INFO, DBG_SESSION, ("Share '%wZ' found: RootId %I64x\n", pFolderName, pShare->RootId));

    FUSE_CONTEXT ctx = {pDevExt, pShare};
    FuseCacheInit(&pShare->FuseEntryCache, ctx, FUSE_CACHE_MAX_ELEMENTS, FUSE_CACHE_TTL);

    pShare->ShareState = HWFS_SHARE_STATE_ALLOCATED | HWFS_SHARE_STATE_CONNECTED;

    return STATUS_SUCCESS;
}

BOOLEAN
HostReconnectShare(
    PHWFS_DEVICE_EXTENSION  pDevExt,
    PHWFS_SHARE             pShare,
    UINT64                  ShareGeneration)
{
    NTSTATUS  Status;

    if ((pShare->ShareState & HWFS_SHARE_STATE_ALLOCATED) == 0) {
        HWFS_DBG_PRINT(DBG_ERR, DBG_ANY,
            ("HostReconnectShare() called for non-allocated share\n"));
        return FALSE;
    }

    ExAcquireFastMutex(&pShare->ReconnectMutex);

    // While reconnect we check that ShareGeneration matches.
    // If it doesn't, this means that another thread already reconnected the share.
    HWFS_DBG_PRINT(DBG_INFO, DBG_SESSION,
        ("Reconnect share '%wZ', Generation %I64u, pShare = %p\n",
            &pShare->ShareName, ShareGeneration, pShare));

    if (pShare->ShareGeneration != ShareGeneration) {
        // Another thread reconnected the share.
        ExReleaseFastMutex(&pShare->ReconnectMutex);

        HWFS_DBG_PRINT(DBG_INFO, DBG_SESSION,
            ("Share reconnected in another thread; Valid = %d\n", pShare->ShareName, HostShareIsConnected(pShare)));

        return HostShareIsConnected(pShare);
    }

    InterlockedIncrement64((volatile __int64*)&pShare->ShareGeneration);

    HostShareSetConnected(pShare, FALSE);
    Status = ConnectShareHelper(pDevExt, pShare);

    ExReleaseFastMutex(&pShare->ReconnectMutex);

    FuseCacheClear(pShare->FuseEntryCache);

    if (NT_SUCCESS(Status)) {
        HWFS_DBG_PRINT(DBG_INFO, DBG_SESSION, ("Share reconnected\n"));
        return TRUE;
    } else {
        HWFS_DBG_PRINT(DBG_WARN, DBG_SESSION, ("Share reconnect failed: 0x%x\n", Status));
        return FALSE;
    }
}

NTSTATUS HostDisconnectShare(
    PHWFS_DEVICE_EXTENSION  pDevExt,
    PHWFS_SHARE             pShare)
{
    if (pShare == NULL || (pShare->ShareState & HWFS_SHARE_STATE_ALLOCATED) == 0) {
        HWFS_DBG_PRINT(DBG_TRACE, DBG_SESSION, ("Disconnect invalid or null share\n"));
        return STATUS_SUCCESS;
    }

    HWFS_DBG_PRINT(DBG_INFO, DBG_SESSION, ("Disconnect share '%wZ', pShare = %p; RootId = %x\n",
        &pShare->ShareName, pShare, pShare->RootId));

    FuseCacheRelease(pShare->FuseEntryCache);

    ExFreePoolWithTag(pShare->ShareName.Buffer, 'mNhS');

    pShare->ShareState = 0;

    return STATUS_SUCCESS;
}

#define WIN_100NS_UNITS_PER_SECOND 10000000L
#define WIN_100NS                  100
#define UNIXTIME_DIFF_CONSTANT     116444736000000000LL

UINT64
UnixTimeToWindows(
    UINT64  time,
    UINT32  nsec)
{
    // convert to 100ns units
    INT64 WinTime = (INT64) time * WIN_100NS_UNITS_PER_SECOND + nsec / WIN_100NS + UNIXTIME_DIFF_CONSTANT;
    return WinTime;
}

static
VOID
WindowsTimeToUnix(
    IN  UINT64   FileTime,
    OUT PUINT64  psec,
    OUT PUINT32  pnsec)
{
    INT64 t = FileTime - UNIXTIME_DIFF_CONSTANT;
    *psec = (UINT64)(t / WIN_100NS_UNITS_PER_SECOND);
    *pnsec = (UINT32)(t % WIN_100NS_UNITS_PER_SECOND * WIN_100NS);
}


ULONG
PosixAttr2FileAttr(
    IN  const struct fuse_attr* attr,
    IN  const CHAR* name,
    IN  int namelen)
{
    ULONG AccessMode;
    ULONG FileAttr = (attr->mode) & S_IFMT;
    ULONG res = 0;

    const CHAR* filename;
    int filenameLen;

    // Extract filename
    filenameLen = 0;
    for (filename = name + namelen; filename != name; filenameLen++, filename--) {
        if (filename[-1] == '\\')
            break;
    }

    if (FileAttr == S_IFDIR) {
        res |= FILE_ATTRIBUTE_DIRECTORY;
    } else if (FileAttr != S_IFREG && FileAttr != S_IFLNK) {
        res |= FILE_ATTRIBUTE_HIDDEN;
    }

    AccessMode = attr->mode & UNIX_ACCESS_MODE_MASK; // 07777
    if ((AccessMode & UNIX_ACCESS_MODE_WRITE) == 0) { // 0222
        res |= FILE_ATTRIBUTE_READONLY;
    }

#define DOTDOT_NAME_SIZE (sizeof("..") - 1)

    // set hidden attribute to files that starts with dot
    if (filenameLen > 1 && filename[0] == '.') {
        if (filenameLen != DOTDOT_NAME_SIZE || filename[1] != '.') {
            res |= FILE_ATTRIBUTE_HIDDEN;
        }
    }

    if (res == 0) {
        res = FILE_ATTRIBUTE_NORMAL;
    }

    return res;
}

static
VOID
FillFileInfo(
    IN  PHWFS_DEVICE_EXTENSION  pDevExt,
    IN  PHWFS_FILE              pHwfsFile,
    IN  PFUSE_ATTR              pAttr,
    OUT PHWFS_FILE_INFO         pFileInfo)
{
    FILE_BASIC_INFORMATION* bi = &pFileInfo->BasicInfo;
    FILE_STANDARD_INFORMATION* si = &pFileInfo->StandardInfo;

    bi->FileAttributes = PosixAttr2FileAttr(
        pAttr, pHwfsFile->Utf8FileName, (int)strlen(pHwfsFile->Utf8FileName));
    bi->CreationTime.QuadPart = UnixTimeToWindows(pAttr->atime, pAttr->atimensec);
    bi->LastAccessTime.QuadPart = UnixTimeToWindows(pAttr->atime, pAttr->atimensec);
    bi->LastWriteTime.QuadPart = UnixTimeToWindows(pAttr->mtime, pAttr->mtimensec);
    bi->ChangeTime.QuadPart = UnixTimeToWindows(pAttr->ctime, pAttr->ctimensec);

    RtlZeroMemory(si, sizeof(*si));
    si->Directory = (bi->FileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? TRUE : FALSE;
    if (!si->Directory) {
        si->EndOfFile.QuadPart = pAttr->size;
        si->AllocationSize.QuadPart = ROUND_TO_PAGES(si->EndOfFile.QuadPart);
        // Block size is always 512, see 'man stat'
#define LINUX_STATFS_BLOCK_SIZE 512

        // Choose maximum from returned from host value and rounded to pages file-size
        if (si->AllocationSize.QuadPart < (LONGLONG)(LINUX_STATFS_BLOCK_SIZE * pAttr->blocks)) {
            si->AllocationSize.QuadPart = LINUX_STATFS_BLOCK_SIZE * pAttr->blocks;
        }
    }
}

static
NTSTATUS
HostSetFileAttributesInternal(
    IN  PHWFS_DEVICE_EXTENSION  pDevExt,
    IN  PHWFS_FILE              pHwfsFile,
    struct fuse_setattr_in*     pFuseAttrs,
    OUT PHWFS_FILE_INFO         pFileInfo)
{
    if (!pHwfsFile->IsValid) {
        return STATUS_INVALID_HANDLE;
    }

    NTSTATUS Status;
    PVIOFS_INKERNEL_REQUEST pRequest;
    PVIOFSCALL_EXTENSION    pRequestExt;

    PFUSE_SETATTR_IN in;
    PFUSE_SETATTR_OUT out;

    HWFS_DBG_PRINT(DBG_TRACE, DBG_FWRITE, ("SetFileAttributes"));

    pRequest = AllocViofsRequestWithBuffer(
        pDevExt,
        sizeof(*in),
        sizeof(*out),
        NULL, 0, NULL, 0);
    if (NULL == pRequest) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    FuseHeaderInit(pRequest, pHwfsFile->ShareId,
        FUSE_SETATTR, pHwfsFile->FuseNodeId, sizeof(in->setattr));

    pRequestExt = pRequest->CallerContext;

    in = (PFUSE_SETATTR_IN)pRequestExt->FuseInHdr;
    RtlCopyMemory(&in->setattr, pFuseAttrs, sizeof(*pFuseAttrs));

    in->setattr.fh = pHwfsFile->FuseHandle;
    in->setattr.valid |= FATTR_FH;

    Status = SubmitViofsRequest(pDevExt, pRequest);
    if (!NT_SUCCESS(Status)) {
        goto finish;
    }

    Status = FuseMapError(pRequestExt->FuseOutHdr->error);
    if (!NT_SUCCESS(Status)) {
        goto finish;
    }

    if (pRequestExt->BytesReturned < sizeof(*out)) {
        HWFS_DBG_PRINT(DBG_ERR, DBG_ANY,
            ("Unexpected return from FUSE_SETATTR: %u\n", pRequestExt->BytesReturned));
        return STATUS_SUCCESS;
    }

    out = (PFUSE_SETATTR_OUT)pRequestExt->FuseOutHdr;
    FillFileInfo(pDevExt, pHwfsFile, &out->attr.attr, pFileInfo);

finish:
    FreeViofsRequest(pRequest);

    if (!NT_SUCCESS(Status)) {
        HWFS_DBG_PRINT(DBG_ERR, DBG_FWRITE, ("FUSE_SETATTR() failed: 0x%x", Status));
        HWFS_DBG_ENDFUNC(Status);
    }

    return Status;
}

NTSTATUS
HostSetBasicFileAttributes(
    IN  PHWFS_DEVICE_EXTENSION  pDevExt,
    IN  PHWFS_FILE              pHwfsFile,
    IN  UINT32                  FileAttributes,
    IN  INT64                   AccessTime,
    IN  INT64                   WriteTime,
    IN  INT64                   ChangeTime,
    OUT PHWFS_FILE_INFO         pFileInfo)
{
    NTSTATUS Status;
    struct fuse_setattr_in attrs = { 0 };
    if (!pHwfsFile->IsValid) {
        return STATUS_INVALID_HANDLE;
    }

    if (FileAttributes != (UINT32)-1 && FileAttributes != 0) {
        if (pHwfsFile->IsDir) {
            FileAttributes |= FILE_ATTRIBUTE_DIRECTORY;
        } else {
            FileAttributes &= ~FILE_ATTRIBUTE_DIRECTORY;
        }

        if (FileAttributes != pFileInfo->BasicInfo.FileAttributes) {
            attrs.valid |= FATTR_MODE;
            attrs.mode = UNIX_ACCESS_MODE_RWRR; /* 0644, -rw-r--r- */
            if ((FileAttributes & FILE_ATTRIBUTE_DIRECTORY) && pHwfsFile->IsDir) {
                attrs.mode |= UNIX_ACCESS_MODE_DIR; // 040111
            }
            if (FileAttributes & FILE_ATTRIBUTE_READONLY) {
                attrs.mode &= ~UNIX_ACCESS_MODE_WRITE; // ~0222;
            }
        }
    }

    if (AccessTime != 0 && AccessTime != -1
            && AccessTime != pFileInfo->BasicInfo.LastAccessTime.QuadPart) {
        attrs.valid |= FATTR_ATIME;
        WindowsTimeToUnix(AccessTime, &attrs.atime, &attrs.atimensec);
    }

    if (WriteTime != 0 && WriteTime != -1
            && WriteTime != pFileInfo->BasicInfo.LastWriteTime.QuadPart) {
        attrs.valid |= FATTR_MTIME;
        WindowsTimeToUnix(WriteTime, &attrs.mtime, &attrs.mtimensec);
    }

    if (ChangeTime != 0 && ChangeTime != -1
            && (INT64)ChangeTime != pFileInfo->BasicInfo.ChangeTime.QuadPart) {
        attrs.valid |= FATTR_CTIME;
        WindowsTimeToUnix(ChangeTime, &attrs.ctime, &attrs.ctimensec);
    }

    if (attrs.valid == 0) {
        return STATUS_SUCCESS;
    }

    Status = HostSetFileAttributesInternal(pDevExt, pHwfsFile, &attrs, pFileInfo);
    if (NT_SUCCESS(Status)) {
        HWFS_DBG_PRINT(DBG_TRACE, DBG_FWRITE,
            ("SetFileSize() success. FileSize: 0x%I64x; AllocationSize 0x%I64x",
                pFileInfo->StandardInfo.EndOfFile.QuadPart,
                pFileInfo->StandardInfo.AllocationSize.QuadPart));
    } else {
        HWFS_DBG_PRINT(DBG_ERR, DBG_FWRITE, ("SetFileSize() failed: 0x%x", Status));
    }
    return Status;
}

// Lookup single component in the specified directory
static
NTSTATUS
FuseLookupRequest(
    IN  PHWFS_DEVICE_EXTENSION  pDevExt,
    IN  UINT32                  ShareId,
    IN  UINT64                  FuseNodeId, // Directory where lookup for the file
    IN  PCHAR                   FileName,   // only file name in UTF-8 without path!
    OUT PFUSE_ENTRY_OUT         pEntry)
{
    NTSTATUS Status;
    PVIOFS_INKERNEL_REQUEST pRequest;
    PVIOFSCALL_EXTENSION    pRequestExt;
    int err;

    if (strlen(FileName) == 0) {
        FileName = ".";
    }
    // skip starting '\'
    if (FileName[0] == '\\') {
        FileName++;
    }

    ULONG NameSize = (ULONG)strlen(FileName) + 1;

    CHAR* InNamePtr;
    FUSE_LOOKUP_OUT *out;

    pRequest = AllocViofsRequestWithBuffer(
        pDevExt,
        sizeof(FUSE_IN_HEADER) + NameSize,
        sizeof(*out),
        NULL, 0, NULL, 0);
    if (NULL == pRequest) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    pRequestExt = pRequest->CallerContext;

    FuseHeaderInit(pRequest, ShareId, FUSE_LOOKUP, FuseNodeId, NameSize);

    InNamePtr = (CHAR*)(pRequestExt->FuseInHdr + 1);
    err = strcpy_s(InNamePtr, NameSize, FileName);
    if (err != 0) {
        Status = STATUS_INTERNAL_ERROR;
        goto finish;
    }

    Status = SubmitViofsRequest(pDevExt, pRequest);
    if (!NT_SUCCESS(Status)) {
        goto finish;
    }

    Status = FuseMapError(pRequestExt->FuseOutHdr->error);
    if (!NT_SUCCESS(Status)) {
        goto finish;
    }

    out = (FUSE_LOOKUP_OUT *)pRequestExt->FuseOutHdr;

    if (pRequestExt->BytesReturned != sizeof(*out)) {
        HWFS_DBG_PRINT(DBG_ERR, DBG_ANY, ("Lookup returned wierd size\n"));
    }

    RtlCopyMemory(pEntry, &out->entry, sizeof(*pEntry));

finish:
    FreeViofsRequest(pRequest);

    if (!NT_SUCCESS(Status)) {
        HWFS_DBG_ENDFUNC(Status);
    }

    return Status;
}


NTSTATUS
FuseForgetRequest(
    IN  PHWFS_DEVICE_EXTENSION  pDevExt,
    IN  UINT32                  ShareId,
    IN  UINT64                  FuseNodeId,
    IN  UINT64                  NLookup)
{
    NTSTATUS Status;
    PVIOFS_INKERNEL_REQUEST pRequest;
    PVIOFSCALL_EXTENSION    pRequestExt;
    PFUSE_FORGET_IN         in;

    pRequest = AllocViofsRequestWithBuffer(
        pDevExt,
        sizeof(*in),
        sizeof(FUSE_OUT_HEADER),
        NULL, 0, NULL, 0);
    if (NULL == pRequest) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    pRequestExt = pRequest->CallerContext;

    FuseHeaderInit(pRequest, ShareId, FUSE_FORGET, FuseNodeId, sizeof(in->forget));
    in = (PFUSE_FORGET_IN)pRequestExt->FuseInHdr;
    in->forget.nlookup = NLookup;

    Status = SubmitViofsRequest(pDevExt, pRequest);
    if (!NT_SUCCESS(Status)) {
        goto finish;
    }

    // There is no reply for Forget-request. Don't look at out-header.

finish:
    if (!NT_SUCCESS(Status)) {
        HWFS_DBG_PRINT(DBG_ERR, DBG_ANY,
            ("Forget Request for %I64x failed with 0x%x\n", Status));
    }
    FreeViofsRequest(pRequest);
    return Status;
}

NTSTATUS HostLookupEntry(
    IN  PHWFS_DEVICE_EXTENSION  pDevExt,
    IN  PHWFS_SHARE             pShare,
    IN  UINT64                  DirNodeId,
    IN  PCHAR                   pUtf8FileName,
    OUT struct fuse_entry_out* pFuseEntry)
{
    NTSTATUS Status;

    Status = FuseLookupRequest(pDevExt, pShare->ShareId, DirNodeId, pUtf8FileName, pFuseEntry);
    if (!NT_SUCCESS(Status)) {
        return Status;
    }
    if (pFuseEntry->nodeid != pShare->RootId) {
        FuseForgetRequest(pDevExt, pShare->ShareId, pFuseEntry->nodeid, 1);
    }
    return STATUS_SUCCESS;
}

// Lookup path till file name.
// Locates NodeId where file-name is located and return file-name
// Name must not start with '\'
static
NTSTATUS
FuseIteratePath(
    IN     PHWFS_DEVICE_EXTENSION  pDevExt,
    IN     PHWFS_SHARE             pShare,
    IN     PCHAR                   szFullPath,
    IN OUT PUINT64                 pParentNodeId,
    OUT    PCHAR*                  ppFileName
)
{
    UINT64 PrevParent = 0;
    UINT64 RootParent = *pParentNodeId;
    *ppFileName = szFullPath;

    NTSTATUS Status;
    PCHAR pLastSlash = strrchr(szFullPath, '\\');
    if (pLastSlash) {
        *pLastSlash = '\0';
        UINT64 FuseNodeId;
        FUSE_CACHE_RESULT Result = FuseCacheGet(pShare->FuseEntryCache, szFullPath, &FuseNodeId);
        *pLastSlash = '\\';

        if (Result == SUCCESS) {
            *pParentNodeId = FuseNodeId;
            *ppFileName = pLastSlash + 1;
            return STATUS_SUCCESS;
        }
    }

    while (1) {
        FUSE_ENTRY_OUT LookupEntry;
        PCHAR pSlash = strchr(*ppFileName, '\\');
        if (pSlash == NULL) {
            return STATUS_SUCCESS;
        }

        *pSlash = '\0'; // in the **ppFileName there is a single component name
        Status =
            FuseLookupRequest(pDevExt, pShare->ShareId, *pParentNodeId, *ppFileName, &LookupEntry);
        *pSlash = '\\'; // Restore path

        if (!NT_SUCCESS(Status)) {
            if (Status == STATUS_OBJECT_NAME_NOT_FOUND) {
                return STATUS_OBJECT_PATH_NOT_FOUND;
            }
            return Status;
        }
        // Must be directory
        if ((LookupEntry.attr.mode & S_IFMT) != S_IFDIR) {
            return STATUS_OBJECT_PATH_NOT_FOUND;
        }

        // ToDo: add handling of Links

        PrevParent = LookupEntry.nodeid;
        *pParentNodeId = LookupEntry.nodeid;
        *ppFileName = pSlash + 1;

        *pSlash = '\0';
        FuseCacheAdd(pShare->FuseEntryCache, szFullPath, LookupEntry.nodeid);
        *pSlash = '\\';
    }
}

BOOLEAN
StartsWithSlash(
    IN  PUNICODE_STRING         pFileName)
{
    return pFileName->Length != 0 && pFileName->Buffer[0] == L'\\';
}

// Allocate UTF8 string (null-terminated) for path
static
PCHAR
AllocateUtf8Path(
    IN  PUNICODE_STRING         pFileName,
    IN  BOOLEAN                 SkipLeadSlash
)
{
    NTSTATUS Status;
    PCHAR szFileName;
    UNICODE_STRING FileName2;
    ULONG utf8_size;

    RtlCopyMemory(&FileName2, pFileName, sizeof(FileName2));
    if (SkipLeadSlash && StartsWithSlash(&FileName2)) {
        FileName2.Length -= sizeof(WCHAR);
        FileName2.Buffer++;
    }

    // Allocate utf8 string with file name
    utf8_size = 0;
    Status = RtlUnicodeToUTF8N(NULL, 0, &utf8_size, FileName2.Buffer, FileName2.Length);
    ASSERT(Status == 0 || Status == STATUS_BUFFER_TOO_SMALL);
    szFileName = ExAllocatePoolWithTag(NonPagedPool, utf8_size + sizeof(char), 'mNlF');
    if (szFileName == NULL) {
        return NULL;
    }

    Status = RtlUnicodeToUTF8N(szFileName, utf8_size, &utf8_size, FileName2.Buffer, FileName2.Length);
    ASSERT(Status == 0);
    szFileName[utf8_size] = '\0';
    return szFileName;
}

static
VOID
FreeUtf8Path(
    IN PCHAR path)
{
    ExFreePoolWithTag(path, 'mNlF');
}

static
NTSTATUS
FuseLookupPath(
    IN  PHWFS_DEVICE_EXTENSION  pDevExt,
    IN  PHWFS_SHARE             pShare,
    IN  PCHAR                   szFullPath, // Full path in UTF8!
    OUT PFUSE_ENTRY_OUT         pLookup)
{
    // Warning! Here we have a memory leak.
    // There should be reference count for each NodeId.
    // And we should call forget when we don't need NodeId anymore

    NTSTATUS    Status;
    PCHAR       szFileName;

    // Go from left to right and lookup components
    UINT64 ParentNode = pShare->RootId;
    Status = FuseIteratePath(pDevExt, pShare, szFullPath, &ParentNode, &szFileName);
    if (!NT_SUCCESS(Status)) {
        HWFS_DBG_PRINT(DBG_ERR, DBG_FOPEN, ("Failed to iterate path %s\n", szFullPath));
        return Status;
    }

    Status = FuseLookupRequest(pDevExt, pShare->ShareId, ParentNode, szFileName, pLookup);
    if (!NT_SUCCESS(Status)) {
        HWFS_DBG_PRINT(DBG_WARN, DBG_FOPEN, ("Failed to open %s in its dir %s\n", szFileName, szFullPath));
    }

    return Status;
}

static
UINT32
AccessToUnix(
    ACCESS_MASK AccessMask,
    BOOLEAN     Truncate)
{
    if (AccessMask & FILE_EXECUTE)
        AccessMask |= FILE_READ_DATA;

    switch (AccessMask & (FILE_READ_DATA | FILE_WRITE_DATA)) {
    case FILE_WRITE_DATA:
        return O_WRONLY | (Truncate ? O_TRUNC : 0);
    case FILE_READ_DATA | FILE_WRITE_DATA:
        return O_RDWR | (Truncate ? O_TRUNC : 0);
    default:
        if (AccessMask & FILE_APPEND_DATA) {
            return O_RDWR;
        }
        return O_RDONLY;
    }
}

static
NTSTATUS
FuseOpenRequest(
    IN  PHWFS_DEVICE_EXTENSION  pDevExt,
    IN  PHWFS_SHARE             pShare,
    IN  UINT64                  NodeId,
    IN  BOOLEAN                 IsDir,
    IN  BOOLEAN                 Truncate,
    IN  ACCESS_MASK             Access,
    IN  PNT_CREATE_PARAMETERS   pNtCreateParams,
    OUT PUINT64                 pFuseHandle)
{
    NTSTATUS                Status;
    PFUSE_OPEN_IN           pOpenIn;
    PFUSE_OPEN_OUT          pOpenOut;
    PVIOFS_INKERNEL_REQUEST pRequest;
    PVIOFSCALL_EXTENSION    pRequestExt;

    pRequest = AllocViofsRequestWithBuffer(
        pDevExt,
        sizeof(*pOpenIn),
        sizeof(*pOpenOut),
        NULL, 0, NULL, 0);
    if (NULL == pRequest) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    pRequestExt = pRequest->CallerContext;

    UINT32 FuseOp = IsDir ? FUSE_OPENDIR : FUSE_OPEN_EXTENDED;

    FuseHeaderInit(pRequest, pShare->ShareId, FuseOp, NodeId, sizeof(pOpenIn->open));

    pOpenIn = (FUSE_OPEN_IN*)pRequestExt->FuseInHdr;
    pOpenIn->open.flags = IsDir ? (O_RDONLY | O_DIRECTORY) : AccessToUnix(Access, Truncate);
    pOpenIn->open.open_flags = 0;
    pOpenIn->open.share_mode = pNtCreateParams->ShareAccess;

    Status = SubmitViofsRequest(pDevExt, pRequest);
    if (!NT_SUCCESS(Status)) {
        goto finish;
    }

    Status = FuseMapError(pRequestExt->FuseOutHdr->error);
    if (!NT_SUCCESS(Status)) {
        goto finish;
    }

    pOpenOut = (FUSE_OPEN_OUT*)pRequestExt->FuseOutHdr;
    *pFuseHandle = pOpenOut->open.fh;

finish:
    FreeViofsRequest(pRequest);

    if (!NT_SUCCESS(Status)) {
        HWFS_DBG_ENDFUNC(Status);
    }

    return Status;
}

static
NTSTATUS
FuseCreateDir(
    IN  PHWFS_DEVICE_EXTENSION  pDevExt,
    IN  PHWFS_SHARE             pShare,
    IN  UINT64                  ParentNodeId,
    IN  PCHAR                   FileName,
    IN  UINT32                  Mode,
    OUT PFUSE_ENTRY_OUT         pEntry)
{
    NTSTATUS Status;
    PVIOFS_INKERNEL_REQUEST pRequest;
    PVIOFSCALL_EXTENSION    pRequestExt;
    int err;

    ULONG NameSize = (ULONG)strlen(FileName) + 1;

    CHAR* InNamePtr;
    PFUSE_MKDIR_IN  in;
    PFUSE_MKDIR_OUT out;

    pRequest = AllocViofsRequestWithBuffer(
        pDevExt,
        sizeof(*in) + NameSize,
        sizeof(*out),
        NULL, 0, NULL, 0);
    if (NULL == pRequest) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    pRequestExt = pRequest->CallerContext;

    FuseHeaderInit(pRequest, pShare->ShareId, FUSE_MKDIR, ParentNodeId,
        NameSize + sizeof(struct fuse_mkdir_in));

    in = (PFUSE_MKDIR_IN)pRequestExt->FuseInHdr;
    in->mkdir.mode = Mode;
    in->mkdir.umask = 0;
    InNamePtr = (CHAR*)(in + 1);
    err = strcpy_s(InNamePtr, NameSize, FileName);
    if (err != 0) {
        Status = STATUS_INTERNAL_ERROR;
        goto finish;
    }

    Status = SubmitViofsRequest(pDevExt, pRequest);
    if (!NT_SUCCESS(Status)) {
        goto finish;
    }

    Status = FuseMapError(pRequestExt->FuseOutHdr->error);
    if (!NT_SUCCESS(Status)) {
        goto finish;
    }

    out = (PFUSE_MKDIR_OUT)pRequestExt->FuseOutHdr;

    if (pRequestExt->BytesReturned != sizeof(*out)) {
        HWFS_DBG_PRINT(DBG_ERR, DBG_ANY, ("Lookup returned wierd size\n"));
    }

    RtlCopyMemory(pEntry, &out->entry, sizeof(*pEntry));

finish:
    FreeViofsRequest(pRequest);

    if (!NT_SUCCESS(Status)) {
        HWFS_DBG_ENDFUNC(Status);
    }

    return Status;
}

static
NTSTATUS
FuseCreateFile(
    IN  PHWFS_DEVICE_EXTENSION  pDevExt,
    IN  PHWFS_SHARE             pShare,
    IN  UINT64                  ParentNodeId,
    IN  PCHAR                   FileName,
    IN  ACCESS_MASK             Access,
    IN  PNT_CREATE_PARAMETERS   pNtCreateParams,
    IN  UINT32                  Mode,
    OUT PFUSE_ENTRY_OUT         pEntry,
    OUT PUINT64                 pFuseHandle)
{
    NTSTATUS Status;
    PVIOFS_INKERNEL_REQUEST pRequest;
    PVIOFSCALL_EXTENSION    pRequestExt;
    int err;

    ULONG NameSize = (ULONG)strlen(FileName) + 1;

    CHAR* InNamePtr;
    PFUSE_CREATE_IN  in;
    PFUSE_CREATE_OUT out;

    pRequest = AllocViofsRequestWithBuffer(
        pDevExt,
        sizeof(*in) + NameSize,
        sizeof(*out),
        NULL, 0, NULL, 0);
    if (NULL == pRequest) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    pRequestExt = pRequest->CallerContext;

    FuseHeaderInit(pRequest, pShare->ShareId, FUSE_CREATE_EXTENDED, ParentNodeId,
        NameSize + sizeof(struct fuse_create_in));

    in = (PFUSE_CREATE_IN)pRequestExt->FuseInHdr;
    in->create.mode = Mode;
    in->create.umask = 0;
    in->create.flags = AccessToUnix(Access, FALSE) | O_EXCL;
    in->create.open_flags = 0;
    in->create.share_mode = pNtCreateParams->ShareAccess;

    InNamePtr = (CHAR*)(in + 1);
    err = strcpy_s(InNamePtr, NameSize, FileName);
    if (err != 0) {
        Status = STATUS_INTERNAL_ERROR;
        goto finish;
    }

    HWFS_DBG_PRINT(DBG_TRACE, DBG_FOPEN, ("CreateFile(%s), mode 0x%8x, flags 0x%08x",
        FileName, Mode, in->create.flags));
    Status = SubmitViofsRequest(pDevExt, pRequest);
    if (!NT_SUCCESS(Status)) {
        goto finish;
    }

    Status = FuseMapError(pRequestExt->FuseOutHdr->error);
    if (!NT_SUCCESS(Status)) {
        goto finish;
    }

    out = (PFUSE_CREATE_OUT)pRequestExt->FuseOutHdr;

    if (pRequestExt->BytesReturned < sizeof(*out)) {
        HWFS_DBG_PRINT(DBG_ERR, DBG_ANY, ("CreateFile returned wierd size\n"));
        Status = STATUS_INTERNAL_ERROR;
        goto finish;
    }

    RtlCopyMemory(pEntry, &out->entry, sizeof(*pEntry));
    *pFuseHandle = out->open.fh;

finish:
    FreeViofsRequest(pRequest);

    if (!NT_SUCCESS(Status)) {
        HWFS_DBG_ENDFUNC(Status);
    }

    return Status;
}

NTSTATUS
HwFsUpdateFileInfo(
    IN  PHWFS_DEVICE_EXTENSION  pDevExt,
    IN  PHWFS_FILE              pHwfsFile,
    OUT PHWFS_FILE_INFO         pFileInfo)
{
    if (!pHwfsFile->IsValid) {
        return STATUS_INVALID_HANDLE;
    }

    NTSTATUS Status;
    PVIOFS_INKERNEL_REQUEST pRequest;
    PVIOFSCALL_EXTENSION    pRequestExt;

    PFUSE_GETATTR_IN in;
    PFUSE_GETATTR_OUT out;

    pRequest = AllocViofsRequestWithBuffer(
        pDevExt,
        sizeof(*in),
        sizeof(*out),
        NULL, 0, NULL, 0);
    if (NULL == pRequest) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    FuseHeaderInit(pRequest, pHwfsFile->ShareId,
        FUSE_GETATTR, pHwfsFile->FuseNodeId, sizeof(in->getattr));

    pRequestExt = pRequest->CallerContext;

    in = (PFUSE_GETATTR_IN)pRequestExt->FuseInHdr;
    in->getattr.getattr_flags = FUSE_GETATTR_FH;
    in->getattr.fh = pHwfsFile->FuseHandle;

    Status = SubmitViofsRequest(pDevExt, pRequest);
    if (!NT_SUCCESS(Status)) {
        goto finish;
    }

    Status = FuseMapError(pRequestExt->FuseOutHdr->error);
    if (!NT_SUCCESS(Status)) {
        goto finish;
    }

    if (pRequestExt->BytesReturned < sizeof(*out)) {
        HWFS_DBG_PRINT(DBG_ERR, DBG_ANY,
            ("Unexpected return from FUSE_GETATTR: %u\n", pRequestExt->BytesReturned));
        return STATUS_UNEXPECTED_IO_ERROR;
    }

    out = (PFUSE_GETATTR_OUT)pRequestExt->FuseOutHdr;
    FillFileInfo(pDevExt, pHwfsFile, &out->attr.attr, pFileInfo);

finish:
    FreeViofsRequest(pRequest);

    if (!NT_SUCCESS(Status)) {
        HWFS_DBG_ENDFUNC(Status);
    }

    return Status;
}

NTSTATUS
HwFsSetAllocSize(
    IN  PHWFS_DEVICE_EXTENSION  pDevExt,
    IN  PHWFS_FILE              pHwfsFile,
    IN  UINT64                  NewSize,
    OUT PHWFS_FILE_INFO         pFileInfo)
{
    //
    // This doesn't change file size. But ensures that write to this file,
    // if they are within allocated size, will not fail.
    //

    NTSTATUS Status;
    PVIOFS_INKERNEL_REQUEST pRequest;
    PVIOFSCALL_EXTENSION    pRequestExt;

    PFUSE_FALLOCATE_IN in;

    HWFS_DBG_PRINT(DBG_TRACE, DBG_FWRITE, ("SetAllocSize: NewSize 0x%I64x", NewSize));

    if (!pHwfsFile->IsValid) {
        return STATUS_INVALID_HANDLE;
    }

    if (NewSize == 0) {
        return STATUS_SUCCESS;
    }

    pRequest = AllocViofsRequestWithBuffer(
        pDevExt,
        sizeof(*in),
        sizeof(FUSE_OUT_HEADER),
        NULL, 0, NULL, 0);
    if (NULL == pRequest) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    FuseHeaderInit(pRequest, pHwfsFile->ShareId,
        FUSE_FALLOCATE, pHwfsFile->FuseNodeId, sizeof(in->falloc));

    pRequestExt = pRequest->CallerContext;

    in = (PFUSE_FALLOCATE_IN)pRequestExt->FuseInHdr;
    in->falloc.fh = pHwfsFile->FuseHandle;
    in->falloc.length = NewSize;
    in->falloc.mode = 0x01; // FALLOC_FL_KEEP_SIZE
    in->falloc.offset = 0;

    Status = SubmitViofsRequest(pDevExt, pRequest);
    if (!NT_SUCCESS(Status)) {
        goto finish;
    }

    Status = FuseMapError(pRequestExt->FuseOutHdr->error);
    if (!NT_SUCCESS(Status)) {
        goto finish;
    }

    Status = HwFsUpdateFileInfo(pDevExt, pHwfsFile, pFileInfo);
    if (NT_SUCCESS(Status)) {
        HWFS_DBG_PRINT(DBG_TRACE, DBG_FWRITE,
            ("SetAllocaSize() success. FileSize: 0x%I64x; AllocationSize 0x%I64x",
                pFileInfo->StandardInfo.EndOfFile.QuadPart,
                pFileInfo->StandardInfo.AllocationSize.QuadPart));
    }
finish:
    FreeViofsRequest(pRequest);

    if (!NT_SUCCESS(Status)) {
        HWFS_DBG_PRINT(DBG_ERR, DBG_FWRITE, ("SetAllocSize() failed: 0x%x", Status));
    }

    return Status;
}


NTSTATUS
HwFsSetFileSize(
    IN  PHWFS_DEVICE_EXTENSION  pDevExt,
    IN  PHWFS_FILE              pHwfsFile,
    IN  UINT64                  NewSize,
    OUT PHWFS_FILE_INFO         pFileInfo)
{
    struct fuse_setattr_in attrs = { 0 };
    if (!pHwfsFile->IsValid) {
        return STATUS_INVALID_HANDLE;
    }

    NTSTATUS Status;
    attrs.valid = FATTR_SIZE;
    attrs.size = NewSize;

    Status = HostSetFileAttributesInternal(pDevExt, pHwfsFile, &attrs, pFileInfo);
    if (NT_SUCCESS(Status)) {
        HWFS_DBG_PRINT(DBG_TRACE, DBG_FWRITE,
            ("SetFileSize() success. FileSize: 0x%I64x; AllocationSize 0x%I64x",
                pFileInfo->StandardInfo.EndOfFile.QuadPart,
                pFileInfo->StandardInfo.AllocationSize.QuadPart));
    } else {
        HWFS_DBG_PRINT(DBG_ERR, DBG_FWRITE, ("SetFileSize() failed: 0x%x", Status));
    }
    return Status;
}

NTSTATUS HostCreateFile(
    IN  PHWFS_DEVICE_EXTENSION  pDevExt,
    IN  PHWFS_SHARE             pShare,
    IN  PUNICODE_STRING         pFileName,
    IN  PNT_CREATE_PARAMETERS   pNtCreateParams,
    OUT PHWFS_FILE              pHwfsFile,
    OUT PULONG                  pCreateAction,
    OUT PHWFS_FILE_INFO         pFileInfo)
{
    NTSTATUS Status;

    FUSE_ENTRY_OUT          LookupEntry;
    PCHAR                   szUtf8FileName;
    UINT64                  FuseHandle;
    BOOLEAN                 FileExists;
    BOOLEAN                 IsDir;
    UINT64                  CreateParentNodeId = pShare->RootId;
    ACCESS_MASK             AccessMask;
    BOOLEAN                 SetAllocationSize = FALSE;

    pHwfsFile->IsValid = FALSE;

    if (!HostShareIsConnected(pShare)) {
        return STATUS_INVALID_HANDLE;
    }

    AccessMask = pNtCreateParams->DesiredAccess;
    if (pNtCreateParams->Disposition == FILE_OVERWRITE ||
        pNtCreateParams->Disposition == FILE_OVERWRITE_IF) {
        AccessMask |= FILE_WRITE_DATA;
    } else if (pNtCreateParams->Disposition == FILE_SUPERSEDE) {
        AccessMask |= DELETE;
    }

    HWFS_DBG_PRINT(DBG_TRACE, DBG_FOPEN,
        ("HostCreateFile: CreateOptions: 0x%x, Access 0x%x, Disposition: 0x%x, Attrs: 0x%x, ShareAccess: 0x%x\n",
            pNtCreateParams->CreateOptions, AccessMask,
            pNtCreateParams->Disposition, pNtCreateParams->FileAttributes,
            pNtCreateParams->ShareAccess));

    pHwfsFile->DeleteOnClose = FALSE;
    pHwfsFile->ForcedDeleteOnClose = !!(pNtCreateParams->CreateOptions & FILE_DELETE_ON_CLOSE);

    BOOLEAN OptionDir = !!(pNtCreateParams->CreateOptions & FILE_DIRECTORY_FILE);
    BOOLEAN OptionNonDir = !!(pNtCreateParams->CreateOptions & FILE_NON_DIRECTORY_FILE);

    if (OptionDir && OptionNonDir) {
        return STATUS_INVALID_PARAMETER;
    }

    if (OptionDir) {
        if (pNtCreateParams->Disposition != FILE_CREATE &&
            pNtCreateParams->Disposition != FILE_OPEN &&
            pNtCreateParams->Disposition != FILE_OPEN_IF) {
            HWFS_DBG_PRINT(DBG_WARN, DBG_FOPEN,
                ("Wrong disposition for Directory Open/Create(): 0x%08x.\n",
                    pNtCreateParams->Disposition));
            return STATUS_INVALID_PARAMETER;
        }
    }

    szUtf8FileName = AllocateUtf8Path(pFileName, TRUE);
    if (szUtf8FileName == NULL) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    LookupEntry.nodeid = pShare->RootId;

    Status = FuseLookupPath(pDevExt, pShare, szUtf8FileName, &LookupEntry);
    if (!NT_SUCCESS(Status)) {
        if (Status != STATUS_OBJECT_NAME_NOT_FOUND) {
            HWFS_DBG_PRINT(DBG_WARN, DBG_FOPEN, ("Lookup Failed: 0x%x\n", Status));
            FreeUtf8Path(szUtf8FileName);
            return Status;
        }
        FileExists = FALSE;
    } else {
        FileExists = TRUE;
    }

    if (FileExists) {
        BOOLEAN Truncate = FALSE;

        if (pNtCreateParams->Disposition == FILE_CREATE) {
            Status = STATUS_OBJECT_NAME_COLLISION;
            goto finish;
        }

        IsDir = ((LookupEntry.attr.mode & S_IFMT) == S_IFDIR) ? TRUE : FALSE;
        if (IsDir && OptionNonDir) {
            HWFS_DBG_PRINT(DBG_ERR, DBG_FOPEN, ("OpenFile requested, but HostFile is dir.\n"));
            Status = STATUS_FILE_IS_A_DIRECTORY;
            goto finish;
        } else if (!IsDir && OptionDir) {
            HWFS_DBG_PRINT(DBG_ERR, DBG_FOPEN, ("OpenDir requested, but HostFile is file.\n"));
            Status = STATUS_NOT_A_DIRECTORY;
            goto finish;
        }

        if (pNtCreateParams->Disposition == FILE_SUPERSEDE) {
            *pCreateAction = FILE_SUPERSEDED;
            Truncate = TRUE;
        } else if (pNtCreateParams->Disposition == FILE_OVERWRITE ||
            pNtCreateParams->Disposition == FILE_OVERWRITE_IF) {
            *pCreateAction = FILE_OVERWRITTEN;
            Truncate = TRUE;
        } else {
            *pCreateAction = FILE_OPENED;
        }

        Status = FuseOpenRequest(
            pDevExt,
            pShare,
            LookupEntry.nodeid,
            IsDir,
            Truncate,
            AccessMask,
            pNtCreateParams,
            &FuseHandle);
        if (!NT_SUCCESS(Status)) {
            goto finish;
        }
    } else {
        //
        // File doesn't exists. Create it.
        //
        UINT32 Mode;

        // Fill CreateAction now. It cannot be anything else
        *pCreateAction = FILE_CREATED;

        if (pNtCreateParams->Disposition == FILE_OPEN ||
                pNtCreateParams->Disposition == FILE_OVERWRITE) {
            // we were requested to fail if file doesn't exist
            Status = STATUS_OBJECT_NAME_NOT_FOUND;
            goto finish;
        }

        CreateParentNodeId = pShare->RootId;
        PCHAR  szJustName;
        Status = FuseIteratePath(pDevExt, pShare, szUtf8FileName, &CreateParentNodeId, &szJustName);
        if (!NT_SUCCESS(Status)) {
            HWFS_DBG_PRINT(DBG_INFO, DBG_FWRITE, ("CreateFile: Failed to get parent node of %s\n", szUtf8FileName));
            Status = STATUS_OBJECT_NAME_INVALID;
            goto finish;
        }

        Mode = UNIX_ACCESS_MODE_RWRWR; // 0664; -rw-rw-r--
        if (pNtCreateParams->FileAttributes & FILE_ATTRIBUTE_READONLY) {
            Mode &= ~UNIX_ACCESS_MODE_WRITE; // ~0222;
        }

        if (OptionDir) {
            Mode |= UNIX_ACCESS_MODE_DIR_XXX; // 0111, ---x--x--x
            Status = FuseCreateDir(pDevExt, pShare, CreateParentNodeId, szJustName, Mode, &LookupEntry);
            if (!NT_SUCCESS(Status)) {
                HWFS_DBG_PRINT(DBG_ERR, DBG_FOPEN, ("CreateDir failed\n"));
                goto finish;
            }

            IsDir = TRUE;

            Status = FuseOpenRequest(
                pDevExt,
                pShare,
                LookupEntry.nodeid,
                IsDir,
                FALSE,
                AccessMask,
                pNtCreateParams,
                &FuseHandle);
            if (!NT_SUCCESS(Status)) {
                HWFS_DBG_PRINT(DBG_ERR, DBG_FOPEN, ("OpenHandle after CreateDir failed\n"));
                goto finish;
            }
        } else {
            if (strchr(szJustName, ':') != NULL) {
                HWFS_DBG_PRINT(DBG_INFO, DBG_FOPEN, ("Attempted to create a forbidden type of file (NTFS stream)\n"));
                Status = STATUS_OBJECT_NAME_INVALID;
                goto finish;
            }

            IsDir = FALSE;
            SetAllocationSize = TRUE;
            Status = FuseCreateFile(
                pDevExt,
                pShare,
                CreateParentNodeId,
                szJustName,
                AccessMask,
                pNtCreateParams,
                Mode,
                &LookupEntry,
                &FuseHandle);
            if (!NT_SUCCESS(Status)) {
                HWFS_DBG_PRINT(DBG_ERR, DBG_FOPEN, ("CreateFile failed: 0x%08x\n", Status));
                goto finish;
            }
        }
    }

    pHwfsFile->FuseNodeId = LookupEntry.nodeid;
    pHwfsFile->FuseHandle = FuseHandle;
    pHwfsFile->IsValid = TRUE;
    pHwfsFile->ShareId = pShare->ShareId;
    pHwfsFile->ShareGeneration = pShare->ShareGeneration;
    pHwfsFile->Utf8FileName = szUtf8FileName;
    pHwfsFile->IsDir = IsDir;

    // It makes code simplier if FillFileInfo here, despite it might be updated in SetAllocSize()
    FillFileInfo(pDevExt, pHwfsFile, &LookupEntry.attr, pFileInfo);

    if (*pCreateAction == FILE_SUPERSEDED || *pCreateAction == FILE_OVERWRITTEN) {
        if (pNtCreateParams->AllocationSize.QuadPart > 0) {
            NTSTATUS Status2;
            Status2 = HwFsSetAllocSize(
                pDevExt,
                pHwfsFile,
                pNtCreateParams->AllocationSize.QuadPart,
                pFileInfo);
            if (!NT_SUCCESS(Status2)) {
                HWFS_DBG_PRINT(DBG_WARN, DBG_FOPEN,
                    ("CreateFile(): AllocSize was not configured: 0x%08x\n", Status2));
            }

            //
            // Need to update file attributes.
            //
            if (*pCreateAction == FILE_SUPERSEDED) {
                Status2 = HostSetBasicFileAttributes(
                    pDevExt,
                    pHwfsFile,
                    pNtCreateParams->FileAttributes,
                    0, 0, 0, pFileInfo);
                if (!NT_SUCCESS(Status2)) {
                    HWFS_DBG_PRINT(DBG_WARN, DBG_FOPEN,
                        ("CreateFile(): Failed to setup fileattrs after supersede: 0x%08x\n", Status2));
                }
            }
        }
    } else if (SetAllocationSize && pNtCreateParams->AllocationSize.QuadPart > 0) {
        NTSTATUS Status2 = HwFsSetAllocSize(
            pDevExt,
            pHwfsFile,
            pNtCreateParams->AllocationSize.QuadPart,
            pFileInfo);
        if (!NT_SUCCESS(Status2)) {
            HWFS_DBG_PRINT(DBG_WARN, DBG_FOPEN,
                ("CreateFile(): AllocSize was not configured: 0x%08x\n", Status2));
        }
    }

    HWFS_DBG_PRINT(DBG_INFO, DBG_FOPEN,
        ("CreateFile(); FileHandle = 0x%I64x\n", pHwfsFile->FuseHandle));

finish:
    if (!NT_SUCCESS(Status)) {
        HWFS_DBG_ENDFUNC(Status);
        if (LookupEntry.nodeid != pShare->RootId) {
            FuseForgetRequest(pDevExt, pShare->ShareId, LookupEntry.nodeid, 1);
        }
        FreeUtf8Path(szUtf8FileName);
    }

    return Status;
}

static
NTSTATUS
FuseRelease(
    IN  PHWFS_DEVICE_EXTENSION  pDevExt,
    IN  BOOLEAN                 IsDirectory,
    IN  UINT32                  ShareId,
    IN  UINT64                  FuseNodeId,
    IN  UINT64                  FuseHandle
    )
{
    PVIOFS_INKERNEL_REQUEST pRequest;
    PVIOFSCALL_EXTENSION    pRequestExt;

    FUSE_RELEASE_IN* in;
    NTSTATUS Status;

    pRequest = AllocViofsRequestWithBuffer(
        pDevExt,
        sizeof(*in),
        sizeof(FUSE_OUT_HEADER),
        NULL, 0, NULL, 0);
    if (NULL == pRequest) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    pRequestExt = pRequest->CallerContext;

    FuseHeaderInit(pRequest, ShareId,
        IsDirectory ? FUSE_RELEASEDIR : FUSE_RELEASE, FuseNodeId, sizeof(in->release));
    in = (FUSE_RELEASE_IN*)pRequestExt->FuseInHdr;

    in->release.fh = FuseHandle;
    in->release.flags = 0;
    in->release.lock_owner = 0;
    in->release.release_flags = 0;

    Status = SubmitViofsRequest(pDevExt, pRequest);
    if (NT_SUCCESS(Status)) {
        Status = FuseMapError(pRequestExt->FuseOutHdr->error);
    }

    if (!NT_SUCCESS(Status)) {
        HWFS_DBG_PRINT(DBG_WARN, DBG_FOPEN,
            ("FuseRelease 0x%I64x: Status 0x%x\n", FuseHandle, Status));
    }

    FreeViofsRequest(pRequest);

    return Status;
}

static
NTSTATUS
FuseDeleteFile(
    IN  PHWFS_DEVICE_EXTENSION  pDevExt,
    IN  PHWFS_SHARE             pShare,
    IN  UINT64                  FuseNodeId,
    IN  PCHAR                   szFileName,
    IN  PHWFS_FILE              pHwfsFile
)
{
    NTSTATUS Status;
    PVIOFS_INKERNEL_REQUEST pRequest;
    PVIOFSCALL_EXTENSION    pRequestExt;
    int err;

    ULONG NameSize = (ULONG)strlen(szFileName) + 1;

    CHAR* InNamePtr;

    pRequest = AllocViofsRequestWithBuffer(
        pDevExt,
        sizeof(FUSE_IN_HEADER) + NameSize,
        sizeof(FUSE_OUT_HEADER),
        NULL, 0, NULL, 0);
    if (NULL == pRequest) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    pRequestExt = pRequest->CallerContext;

    FuseHeaderInit(
        pRequest,
        pShare->ShareId,
        pHwfsFile->IsDir ? FUSE_RMDIR : FUSE_UNLINK,
        FuseNodeId,
        NameSize
    );

    InNamePtr = (CHAR *)(pRequestExt->FuseInHdr + 1);
    err = strcpy_s(InNamePtr, NameSize, szFileName);
    if (err != 0) {
        Status = STATUS_INTERNAL_ERROR;
        goto finish;
    }

    Status = SubmitViofsRequest(pDevExt, pRequest);
    if (!NT_SUCCESS(Status)) {
        goto finish;
    }

    Status = FuseMapError(pRequestExt->FuseOutHdr->error);
finish:
    if (pHwfsFile->IsDir) {
        FuseCacheRemove(pShare->FuseEntryCache,  pHwfsFile->Utf8FileName);
    }

    FreeViofsRequest(pRequest);

    return Status;
}

NTSTATUS HostCloseFile(
    IN  PHWFS_DEVICE_EXTENSION  pDevExt,
    IN  PHWFS_SHARE             pShare,
    OUT PHWFS_FILE              pHwfsFile)
{
    NTSTATUS Status;

    if (!pHwfsFile || !pHwfsFile->IsValid) {
        return STATUS_SUCCESS;
    }

    FuseRelease(pDevExt, pHwfsFile->IsDir, pHwfsFile->ShareId, pHwfsFile->FuseNodeId, pHwfsFile->FuseHandle);

    if (pHwfsFile->DeleteOnClose || pHwfsFile->ForcedDeleteOnClose) {
        HWFS_DBG_PRINT(DBG_INFO, DBG_FWRITE, ("Delete On Close %s\n", pHwfsFile->Utf8FileName));

        UINT64      ParentNode = pShare->RootId;
        PCHAR       szFileName;
        Status = FuseIteratePath(pDevExt, pShare, pHwfsFile->Utf8FileName, &ParentNode, &szFileName);
        if (!NT_SUCCESS(Status)) {
            HWFS_DBG_PRINT(DBG_INFO, DBG_FWRITE, ("Failed to iterate delete path %s\n", pHwfsFile->Utf8FileName));
        }

        FuseDeleteFile(pDevExt, pShare, ParentNode, szFileName, pHwfsFile);
    }

    if (pShare->RootId != pHwfsFile->FuseNodeId) {
        FuseForgetRequest(pDevExt, pShare->ShareId, pHwfsFile->FuseNodeId, 1);
    }

    FreeUtf8Path(pHwfsFile->Utf8FileName);
    pHwfsFile->IsValid = FALSE;

    return STATUS_SUCCESS;
}

NTSTATUS HostReadDirectory(
    IN      PHWFS_DEVICE_EXTENSION  pDevExt,
    IN      PHWFS_FILE              pHwfsFile,
    IN      UINT64                  uOffset,
    IN      PVOID                   pBuffer,
    IN      ULONG                   BufSize,
    OUT     PUINT64                 pBytesTransferred)
{
    NTSTATUS Status;
    PVIOFS_INKERNEL_REQUEST pRequest;
    PVIOFSCALL_EXTENSION pRequestExt;
    FUSE_READ_IN* pFuseReadIn;

    pRequest = AllocViofsRequestWithBuffer(
        pDevExt,
        sizeof(*pFuseReadIn),
        sizeof(FUSE_OUT_HEADER),
        NULL, 0, pBuffer, BufSize);
    if (NULL == pRequest) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    pRequestExt = pRequest->CallerContext;

    FuseHeaderInit(pRequest, pHwfsFile->ShareId, FUSE_READDIRPLUS, pHwfsFile->FuseNodeId, sizeof(*pFuseReadIn));
    pFuseReadIn = (FUSE_READ_IN*)pRequestExt->FuseInHdr;
    pFuseReadIn->read.fh = pHwfsFile->FuseHandle;
    pFuseReadIn->read.offset = uOffset;
    pFuseReadIn->read.size = BufSize;
    pFuseReadIn->read.read_flags = 0;
    pFuseReadIn->read.lock_owner = 0;
    pFuseReadIn->read.flags = 0;

    Status = SubmitViofsRequest(pDevExt, pRequest);
    // may be need pay attention to STATUS_DATA_OVERRUN
    if (!NT_SUCCESS(Status)) {
        goto finish;
    }

    Status = FuseMapError(pRequestExt->FuseOutHdr->error);
    if (!NT_SUCCESS(Status)) {
        goto finish;
    }

    if (pRequestExt->BytesReturned < sizeof(FUSE_OUT_HEADER)) {
        Status = STATUS_DATA_OVERRUN;
        goto finish;
    }
    *pBytesTransferred = pRequestExt->BytesReturned - sizeof(FUSE_OUT_HEADER);

    HWFS_DBG_PRINT(DBG_TRACE, DBG_READDIR, ("ReadDir: BytesRead 0x%x\n",
        (ULONG)*pBytesTransferred));

finish:
    FreeViofsRequest(pRequest);

    if (!NT_SUCCESS(Status)) {
        HWFS_DBG_ENDFUNC(Status);
    }

    return Status;
}

// Returns number of pages in Mdl that stores Size bytes
static
ULONG
MdlPagesCount(
    IN  PMDL                    pMdlBuffer,
    IN  UINT64                  Size)
{
    PMDL  Mdl;
    ULONG PagesCount;
    ULONG PageOffset;
    ULONG BytesLeft;

    // Count number of pages in Mdl
    if (!pMdlBuffer) {
        return 0;
    }

    PagesCount = 0;
    BytesLeft = (ULONG)Size;
    PageOffset = MmGetMdlByteOffset(pMdlBuffer);
    for (Mdl = pMdlBuffer; Mdl != NULL && BytesLeft != 0; Mdl = Mdl->Next) {
        ULONG MdlByteCount = MmGetMdlByteCount(Mdl);
        ULONG ChunkBytes = MIN(MdlByteCount, BytesLeft);
        ULONG total_pages = BYTES_TO_PAGES(PageOffset + ChunkBytes);
        PageOffset = 0;
        BytesLeft -= ChunkBytes;
        PagesCount += total_pages;
    }
    return PagesCount;
}

static
NTSTATUS
HostReadWriteFile(
    IN  PHWFS_DEVICE_EXTENSION  pDevExt,
    IN  BOOLEAN                 IsRead,
    IN  PHWFS_FILE              pHwfsFile,
    IN  UINT64                  uOffset,
    IN  PMDL                    pMdlBuffer,
    IN  UINT64                  Size,
    OUT PUINT64                 pBytesTransferred)
{
    PVIOFS_INKERNEL_REQUEST pRequest;
    PVIOFSCALL_EXTENSION    pRequestExt;

    FUSE_READ_IN*    pFuseReadIn;
    // Initialize with NULL.
    // Old compiler gives false-positive warning about potentially uninitilized var
    FUSE_OUT_HEADER* pFuseReadOut = NULL;

    FUSE_WRITE_IN*  pFuseWriteIn;
    FUSE_WRITE_OUT* pFuseWriteOut = NULL;

    NTSTATUS  Status;
    ULONG     MdlPages;
    ULONG     PagesProcessed;
    ULONG     PageOffset;

    // Go through Mdl and fill the ReadRequest
    Status = STATUS_SUCCESS;
    pRequest = NULL;

    PagesProcessed = 0;
    MdlPages = MdlPagesCount(pMdlBuffer, Size);
    PageOffset = MmGetMdlByteOffset(pMdlBuffer);
    while (MdlPages != 0) {
        ULONG MaxChunkSize;
        ULONG ChunkSize;
        ULONG BytesProcessed = 0;
        ULONG MaxPagesToProcess = MIN(MdlPages, pDevExt->FuseMaxPages);

        ASSERT(Size != 0);

        // Calculate ChunkSize and ChunkPages, taking into account
        // that we should request one more page if chunk crosses the page-boundary
        // and PageOffset is not zero.
        // (If offset is 0xfff, then 2 bytes will consume 2 pages)
        ULONG ChunkPages = BYTES_TO_PAGES((ULONG)Size + PageOffset);
        ChunkPages = MIN(ChunkPages, MaxPagesToProcess);

        if (PageOffset == 0) {
            MaxChunkSize = ChunkPages * PAGE_SIZE;
            ChunkSize = MIN((ULONG)Size, MaxChunkSize);
        } else {
            MaxChunkSize = (PAGE_SIZE - PageOffset) + (ChunkPages - 1) * PAGE_SIZE;
            ChunkSize = MIN((ULONG)Size, MaxChunkSize);
            PageOffset = 0; // reset offset to zero when page-0 processed
        }

        if (IsRead) {
            pRequest = AllocViofsRequestWithMdl(
                pDevExt,
                sizeof(*pFuseReadIn),
                sizeof(*pFuseReadOut),
                PagesProcessed,
                NULL, 0, pMdlBuffer, ChunkSize);
            if (NULL == pRequest) {
                return STATUS_INSUFFICIENT_RESOURCES;
            }
            pRequestExt = pRequest->CallerContext;
            pFuseReadIn = (FUSE_READ_IN *)pRequestExt->FuseInHdr;
            pFuseReadOut = pRequestExt->FuseOutHdr;

            FuseHeaderInit(pRequest, pHwfsFile->ShareId, FUSE_READ, pHwfsFile->FuseNodeId, sizeof(pFuseReadIn->read));
            pFuseReadIn->read.fh = pHwfsFile->FuseHandle;
            pFuseReadIn->read.offset = uOffset;
            pFuseReadIn->read.size = ChunkSize;
            pFuseReadIn->read.read_flags = 0;
            pFuseReadIn->read.lock_owner = 0;
            pFuseReadIn->read.flags = 0;
        } else {
            pRequest = AllocViofsRequestWithMdl(
                pDevExt,
                sizeof(*pFuseWriteIn),
                sizeof(*pFuseWriteOut),
                PagesProcessed,
                pMdlBuffer, ChunkSize, NULL, 0);
            if (NULL == pRequest) {
                return STATUS_INSUFFICIENT_RESOURCES;
            }
            pRequestExt = pRequest->CallerContext;
            pFuseWriteIn = (FUSE_WRITE_IN *)pRequestExt->FuseInHdr;
            pFuseWriteOut = (FUSE_WRITE_OUT *)pRequestExt->FuseOutHdr;

            FuseHeaderInit(pRequest, pHwfsFile->ShareId, FUSE_WRITE, pHwfsFile->FuseNodeId,
                sizeof(pFuseWriteIn->write) + ChunkSize);
            pFuseWriteIn->write.fh = pHwfsFile->FuseHandle;
            pFuseWriteIn->write.offset = uOffset;
            pFuseWriteIn->write.size = ChunkSize;
            pFuseWriteIn->write.write_flags = 0;
            pFuseWriteIn->write.lock_owner = 0;
            pFuseWriteIn->write.flags = 0;
        }

        Status = SubmitViofsRequest(pDevExt, pRequest);
        // may be need pay attention to STATUS_DATA_OVERRUN
        if (!NT_SUCCESS(Status)) {
            goto finish;
        }

        Status = FuseMapError(pRequestExt->FuseOutHdr->error);
        if (!NT_SUCCESS(Status)) {
            goto finish;
        }

        if (IsRead) {
            BytesProcessed = pFuseReadOut->len - sizeof(FUSE_OUT_HEADER);
        } else {
            BytesProcessed = pFuseWriteOut->write.size;
        }
        PagesProcessed += ChunkPages;
        MdlPages -= ChunkPages;
        Size -= BytesProcessed;
        *pBytesTransferred += BytesProcessed;
        uOffset += BytesProcessed;
        if (BytesProcessed != ChunkSize) {
            break;
        }
        FreeViofsRequest(pRequest);
        pRequest = NULL;
    }

finish:
    if (pRequest) {
        FreeViofsRequest(pRequest);
        pRequest = NULL;
    }

    HWFS_DBG_PRINT(DBG_TRACE, DBG_FREAD | DBG_FWRITE,
        ("HostReadWriteFile: Read: %d, BytesTransferred 0x%x; Status 0x%x\n",
        (int)IsRead, (ULONG)*pBytesTransferred, Status));

    if (IsRead && NT_SUCCESS(Status) && *pBytesTransferred == 0) {
        Status = STATUS_END_OF_FILE;
    }

    return Status;
}


NTSTATUS HostReadFile(
    IN  PHWFS_DEVICE_EXTENSION  pDevExt,
    IN  PHWFS_FILE              pHwfsFile,
    IN  UINT64                  uOffset,
    IN  PMDL                    pMdlBuffer,
    IN  UINT64                  Size,
    OUT PUINT64                 pBytesTransferred)
{
    return HostReadWriteFile(
        pDevExt, TRUE, pHwfsFile,
        uOffset, pMdlBuffer, Size, pBytesTransferred);
}

NTSTATUS HostWriteFile(
    IN  PHWFS_DEVICE_EXTENSION  pDevExt,
    IN  PHWFS_FILE              pHwfsFile,
    IN  UINT64                  uOffset,
    IN  PMDL                    pMdlBuffer,
    IN  UINT64                  Size,
    OUT PUINT64                 pBytesTransferred)
{
    return HostReadWriteFile(
        pDevExt, FALSE, pHwfsFile,
        uOffset, pMdlBuffer, Size, pBytesTransferred);
}

static
NTSTATUS
FuseRenameFile(
    IN  PHWFS_DEVICE_EXTENSION  pDevExt,
    IN  PHWFS_SHARE             pShare,
    IN  PHWFS_FILE              pHwfsFile,
    IN  UINT64                  OldDirId,
    IN  PCHAR                   OldName,
    IN  UINT64                  NewDirId,
    IN  PCHAR                   NewName)
{
    NTSTATUS Status;
    PVIOFS_INKERNEL_REQUEST pRequest;
    PVIOFSCALL_EXTENSION    pRequestExt;

    ULONG oldname_size = (ULONG)strlen(OldName) + 1; // zero terminated
    ULONG newname_size = (ULONG)strlen(NewName) + 1;

    CHAR* name_ptr;
    FUSE_OUT_HEADER* out;
    FUSE_RENAME_IN* in;

    pRequest = AllocViofsRequestWithBuffer(
        pDevExt,
        sizeof(FUSE_RENAME_IN) + oldname_size + newname_size,
        sizeof(*out),
        NULL, 0, NULL, 0);
    if (NULL == pRequest) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    pRequestExt = pRequest->CallerContext;

    FuseHeaderInit(pRequest, pShare->ShareId, FUSE_RENAME, OldDirId,
        sizeof(struct fuse_rename_in) + oldname_size + newname_size);

    in = (FUSE_RENAME_IN *)pRequestExt->FuseInHdr;
    in->rename.newdir = NewDirId;

    PCHAR names = (PCHAR)(in + 1);
    RtlCopyMemory(names, OldName, oldname_size);
    RtlCopyMemory(names + oldname_size, NewName, newname_size);

    Status = SubmitViofsRequest(pDevExt, pRequest);
    if (!NT_SUCCESS(Status)) {
        goto finish;
    }

    Status = FuseMapError(pRequestExt->FuseOutHdr->error);
    if (!NT_SUCCESS(Status)) {
        goto finish;
    }

finish:

    if (pHwfsFile->IsDir) {
        FuseCacheRemoveAllWithPrefix(pShare->FuseEntryCache, pHwfsFile->Utf8FileName);
    }

    FreeViofsRequest(pRequest);

    if (!NT_SUCCESS(Status)) {
        HWFS_DBG_ENDFUNC(Status);
    }

    return Status;
}

NTSTATUS HostRenameFile(
    IN  PHWFS_DEVICE_EXTENSION  pDevExt,
    IN  PHWFS_SHARE             pShare,
    IN  PHWFS_FILE              pHwfsFile,
    IN  PUNICODE_STRING         pNewFileName,
    IN  BOOLEAN                 ReplaceIfExists)
{
    NTSTATUS    Status;
    int err;

    if (!pHwfsFile->IsValid) {
        return STATUS_INVALID_HANDLE;
    }

    if (pNewFileName->Length == 0) {
        return STATUS_SUCCESS; // OS will fail that itself
    }

    HWFS_DBG_PRINT(DBG_TRACE, DBG_FWRITE, ("HostRenameFile: '%s' -> '%wZ'; Replace = %u\n",
        pHwfsFile->Utf8FileName, pNewFileName, ReplaceIfExists));

    PCHAR szNewFullPath;
    if (StartsWithSlash(pNewFileName)) {
        szNewFullPath = AllocateUtf8Path(pNewFileName, TRUE);
        if (szNewFullPath == NULL) {
            return STATUS_INSUFFICIENT_RESOURCES;
        }
    } else {
        // We have relative name. Must construct FullyQualified name ourself.
        PCHAR filename = AllocateUtf8Path(pNewFileName, TRUE);
        if (filename == NULL) {
            return STATUS_INSUFFICIENT_RESOURCES;
        }

        PCHAR pSlash = strrchr(pHwfsFile->Utf8FileName, '\\');
        if (pSlash == NULL) {
            szNewFullPath = filename; // File in root. Just use new name.
        } else {
            ULONG nSlashPos = (ULONG)(pHwfsFile->Utf8FileName - pSlash);
            ULONG Size = (ULONG)(nSlashPos + 1 + strlen(filename) + 1);

            szNewFullPath = ExAllocatePoolWithTag(NonPagedPool, Size, 'mNlF');
            if (szNewFullPath == NULL) {
                FreeUtf8Path(filename);
                return STATUS_INSUFFICIENT_RESOURCES;
            }

            RtlCopyMemory(szNewFullPath, pHwfsFile->Utf8FileName, nSlashPos + 1);
            err = strcpy_s(szNewFullPath + nSlashPos + 1, Size - (nSlashPos + 1), filename);
            if (err != 0) {
                FreeUtf8Path(filename);
                return STATUS_INTERNAL_ERROR;
            }
            FreeUtf8Path(filename);
        }
    }

    HWFS_DBG_PRINT(DBG_TRACE, DBG_FWRITE,
        ("HostRenameFile: %s -> '%s' ('%wZ')\n",
        pHwfsFile->Utf8FileName, szNewFullPath, pNewFileName));

    //
    // Lookup old path
    //
    UINT64 OldParentNode = pShare->RootId;
    PCHAR  szOldFileName;
    Status = FuseIteratePath(pDevExt, pShare, pHwfsFile->Utf8FileName, &OldParentNode, &szOldFileName);
    if (!NT_SUCCESS(Status)) {
        HWFS_DBG_PRINT(DBG_INFO, DBG_FWRITE, ("Failed to iterate old path %s\n", pHwfsFile->Utf8FileName));
        FreeUtf8Path(szNewFullPath);
        return Status;
    }

    //
    // Lookup new path
    //
    UINT64      NewParentNode = pShare->RootId;
    PCHAR       szNewFileName;
    Status = FuseIteratePath(pDevExt, pShare, szNewFullPath, &NewParentNode, &szNewFileName);
    if (!NT_SUCCESS(Status)) {
        HWFS_DBG_PRINT(DBG_INFO, DBG_FWRITE, ("Failed to iterate new path %s\n", szNewFullPath));
        FreeUtf8Path(szNewFullPath);
        return Status;
    }

    FUSE_ENTRY_OUT target_out;
    Status = FuseLookupRequest(pDevExt, pShare->ShareId, NewParentNode, szNewFileName, &target_out);
    if (NT_SUCCESS(Status)) {
        HWFS_DBG_PRINT(DBG_INFO, DBG_FWRITE, ("Rename %s -> %s: dest fsentry exists; attr 0x%08x; Replace = %d\n",
            szOldFileName, szNewFileName, (target_out.attr.mode & S_IFMT), ReplaceIfExists));
        if (target_out.nodeid != pShare->RootId) {
            FuseForgetRequest(pDevExt, pShare->ShareId, target_out.nodeid, 1);
        }
        if (!ReplaceIfExists || (target_out.attr.mode & S_IFMT) == S_IFDIR) {
            Status = STATUS_OBJECT_NAME_COLLISION;
        }
    } else {
        Status = STATUS_SUCCESS;
    }

    if (NT_SUCCESS(Status)) {
        Status = FuseRenameFile(
            pDevExt,
            pShare,
            pHwfsFile,
            OldParentNode, szOldFileName,
            NewParentNode, szNewFileName);
    }

    if (NT_SUCCESS(Status)) {
        FreeUtf8Path(pHwfsFile->Utf8FileName);
        pHwfsFile->Utf8FileName = szNewFullPath;
    } else {
        FreeUtf8Path(szNewFullPath);
    }

    HWFS_DBG_PRINT(DBG_TRACE, DBG_FWRITE, ("Rename finished with status 0x%x\n", Status));

    return Status;
}
